package nr.co.mhgames.polyanim.renderer.modifiers;

import nr.co.mhgames.polyanim.util.ColorDef;

/**
 * HSL modifier allows you to adjust animation appearance by
 * Hue-Saturation-Lightness modification. This can be used to create interesting
 * effects, such as desaturation or hue rotation.
 * <p>
 * Note that if both HSL and Color modifiers are used together, HSL is always
 * applied first, then the coloring. Also, while the transformation modifiers
 * (flip, rotate, etc.) can be chained, only one instance of HSL and Color
 * modifiers can be active. So, if you pass several of these modifiers to the
 * renderer, only the last modifier will be applied.
 * 
 * @remark This modifier makes the renderer perform the HSL manipulation on
 *         every single polygon of the animation it renders, so the quite
 *         possible performance hit caused by that should be taken into
 *         consideration.
 * @author Mika Halttunen
 */
public class HSLModifier implements RenderModifier {
	/** Hue, Saturation and Lightness modifications */
	public float hueMod, satMod, lightMod;

	/**
	 * Constructor. The Saturation-Lightness values are between [-100%, 100%]
	 * and indicate the change in the component. Hue is between [-50%, 50%] and
	 * is likewise a change. Zero means no modification.
	 * 
	 * @param hue
	 *            Hue change
	 * @param sat
	 *            Saturation change
	 * @param lightness
	 *            Lightness change
	 */
	public HSLModifier(float hue, float sat, float lightness) {
		assert (hue >= -50 && hue <= 50);
		assert (sat >= -100 && hue <= 100);
		assert (lightness >= -100 && lightness <= 100);
		hueMod = hue;
		satMod = sat;
		lightMod = lightness;
	}


	/**
	 * Modulate the given color by the specified HSL changes. The changes are
	 * applied to the given color.
	 * 
	 * @param color
	 *            Color to modulate
	 */
	public void modulate(ColorDef color) {
		double scale = 0.0100000000000001;

		// Convert to HSL
		float hsl[] = colorToHSL(color);

		// Change the components
		// Hue:
		hsl[0] += scale * hueMod;
		if (hsl[0] < 0.0f)
			hsl[0] += 1.0f;
		else if (hsl[0] > 1.0f)
			hsl[0] -= 1.0f;

		// Sat:
		hsl[1] += scale * satMod;
		if (hsl[1] < 0.0f)
			hsl[1] = 0.0f;
		else if (hsl[1] > 1.0f)
			hsl[1] = 1.0f;

		// Lightness:
		hsl[2] += scale * lightMod;
		if (hsl[2] < 0.0f)
			hsl[2] = 0.0f;
		else if (hsl[2] > 1.0f)
			hsl[2] = 1.0f;

		setColorFromHSL(color, hsl[0], hsl[1], hsl[2], color.alpha / 255.0f);
	}


	/**
	 * Helper method for colorfromHSL(), that maps hue values to either red,
	 * green or blue.
	 */
	private static float hue2RGB(float v1, float v2, float hue) {
		if (hue < 0.0f)
			hue += 1.0f;
		if (hue > 1.0f)
			hue -= 1.0f;
		if ((6.0f * hue) < 1.0f)
			return (v1 + (v2 - v1) * 6.0f * hue);
		if ((2.0f * hue) < 1.0f)
			return v2;
		if ((3.0f * hue) < 2.0f)
			return (v1 + (v2 - v1) * ((2.0f / 3.0f) - hue) * 6.0f);
		return v1;
	}


	/**
	 * Set the color from Hue-Saturation-Lightness and Alpha values. All the
	 * values must be in range [0,1].
	 * 
	 * @param color
	 *            Output color
	 * @param hue
	 *            Hue
	 * @param sat
	 *            Saturation
	 * @param light
	 *            Lightness
	 * @param alpha
	 *            Alpha
	 */
	protected static void setColorFromHSL(ColorDef color, float hue, float sat, float light,
			float alpha) {
		if (sat == 0) {
			color.set(light, light, light, alpha);
			return;
		}

		float v2;
		if (light < 0.5f)
			v2 = light * (1.0f + sat);
		else
			v2 = (light + sat) - (sat * light);

		float v1 = 2.0f * light - v2;
		float r, g, b;
		r = hue2RGB(v1, v2, hue + (1.0f / 3.0f));
		g = hue2RGB(v1, v2, hue);
		b = hue2RGB(v1, v2, hue - (1.0f / 3.0f));

		color.set(r, g, b, alpha);
	}

	/**
	 * Returns the Hue-Saturation-Lightness values from the given color as an
	 * array of three floats. The values are in range [0,1].
	 * 
	 * @param color
	 *            Color
	 * @return HSL as a float array
	 */
	protected static float[] colorToHSL(ColorDef color) {
		float r = color.red / 255.0f;
		float g = color.green / 255.0f;
		float b = color.blue / 255.0f;

		float min = Math.min(r, Math.min(g, b));
		float max = Math.max(r, Math.max(g, b));
		float delta = max - min;

		float hsl[] = new float[3];
		hsl[2] = (max + min) / 2.0f; // Light
		if (delta == 0) { // Grey
			hsl[0] = 0;
			hsl[1] = 0;
			return hsl;
		}

		float hue = 0, sat;
		if (hsl[2] < 0.5f)
			sat = delta / (max + min);
		else
			sat = delta / (2.0f - max - min);

		float dr = (((max - r) / 6.0f) + (delta / 2.0f)) / delta;
		float dg = (((max - g) / 6.0f) + (delta / 2.0f)) / delta;
		float db = (((max - b) / 6.0f) + (delta / 2.0f)) / delta;

		if (r == max)
			hue = db - dg;
		else if (g == max)
			hue = (1.0f / 3.0f) + dr - db;
		else if (b == max)
			hue = (2.0f / 3.0f) + dg - dr;

		if (hue < 0.0f)
			hue += 1.0f;
		if (hue > 1.0f)
			hue -= 1.0f;

		// Assign
		hsl[0] = hue;
		hsl[1] = sat;
		return hsl;
	}

}
